// handlers/file_handlers.go
package handlers

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"mime"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"golang.org/x/sync/semaphore"

	"file-manager/models"
	"file-manager/storage"
	"file-manager/utils"
)

const (
	uploadDir      = "./uploads"
	chunkSize      = 5 * 1024 * 1024   // 5MB chunks
	maxPreviewSize = 100 * 1024 * 1024 // 100MB
)

var (
	uploadSem    = semaphore.NewWeighted(10) // 同时最多处理 10 个文件
	sessionLocks sync.Map                    // key: fileId, value: *sync.Mutex
	bufferPool   = sync.Pool{
		New: func() interface{} {
			buf := make([]byte, 64*1024) // 64KB buffer
			return &buf
		},
	}
)

type FileHandlers struct{}

var File_Handlers = &FileHandlers{}

// ErrorResponse 统一错误响应结构
type ErrorResponse struct {
	Error  string `json:"error"`
	Detail string `json:"detail,omitempty"`
	Code   int    `json:"code,omitempty"`
}

// SuccessResponse 统一成功响应结构
type SuccessResponse struct {
	Message string       `json:"message,omitempty"`
	File    *models.File `json:"file,omitempty"`
	Status  string       `json:"status,omitempty"`
}

// validateFilePath 验证文件路径是否在上传目录内
func (h *FileHandlers) validateFilePath(path string) (bool, error) {
	return utils.IsPathInUploadDir(path, uploadDir)
}

// computeFileMD5 计算文件的MD5
func (h *FileHandlers) computeFileMD5(file io.Reader) (string, error) {
	bufPtr := bufferPool.Get().(*[]byte)
	defer bufferPool.Put(bufPtr)

	hash := md5.New()
	_, err := io.CopyBuffer(hash, file, *bufPtr)
	if err != nil {
		return "", err
	}
	return hex.EncodeToString(hash.Sum(nil)), nil
}

// saveFileToDisk 将文件保存到磁盘
func (h *FileHandlers) saveFileToDisk(file multipart.File, filename string) (string, error) {
	filePath := filepath.Join(uploadDir, utils.SanitizeFilename(filename))
	out, err := os.Create(filePath)
	if err != nil {
		return "", err
	}
	defer out.Close()

	bufPtr := bufferPool.Get().(*[]byte)
	defer bufferPool.Put(bufPtr)
	_, err = io.CopyBuffer(out, file, *bufPtr)
	if err != nil {
		os.Remove(filePath)
		return "", err
	}

	return filePath, nil
}

// ListFiles handles GET /api/files
func (h *FileHandlers) ListFiles(c *gin.Context) {
	parent := c.Query("parent")
	if parent == "" {
		parent = "root"
	}

	result := storage.ListFilesByParent(parent)
	c.JSON(http.StatusOK, result)
}

// CreateFile handles POST /api/files (simple file upload)
func (h *FileHandlers) CreateFile(c *gin.Context) {
	parent := c.PostForm("parent")
	if parent == "" {
		parent = "root"
	}

	file, header, err := c.Request.FormFile("file")
	if err != nil {
		c.JSON(http.StatusBadRequest, ErrorResponse{
			Error:  "Failed to get uploaded file",
			Detail: err.Error(),
		})
		return
	}
	defer file.Close()

	filename := header.Filename
	fileSize := header.Size

	if filename == "" {
		c.JSON(http.StatusBadRequest, ErrorResponse{Error: "Invalid filename"})
		return
	}

	// 计算MD5
	md5Sum, err := h.computeFileMD5(file)
	if err != nil {
		c.JSON(http.StatusInternalServerError, ErrorResponse{
			Error:  "Failed to read file for MD5",
			Detail: err.Error(),
		})
		return
	}

	// 检查重复文件
	existing := storage.FindFileByMD5(md5Sum, fileSize, filename)
	if existing != nil {
		c.JSON(http.StatusOK, SuccessResponse{
			File:    existing,
			Message: "File already exists (instant upload)",
		})
		return
	}

	// 重置文件指针
	_, err = file.Seek(0, 0)
	if err != nil {
		c.JSON(http.StatusInternalServerError, ErrorResponse{
			Error:  "Cannot reset file pointer",
			Detail: err.Error(),
		})
		return
	}

	// 保存文件到磁盘
	filePath, err := h.saveFileToDisk(file, filename)
	if err != nil {
		c.JSON(http.StatusInternalServerError, ErrorResponse{
			Error:  "Failed to save file content",
			Detail: err.Error(),
		})
		return
	}

	// 创建文件记录
	fileID := uuid.New().String()
	newFile := &models.File{
		ID:        fileID,
		Name:      filename,
		Size:      fileSize,
		Type:      "file",
		Parent:    parent,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
		MD5:       md5Sum,
		Path:      filePath,
	}

	storage.AddFile(newFile)

	c.JSON(http.StatusOK, SuccessResponse{File: newFile})
}

// DeleteFile handles DELETE /api/files/:id
func (h *FileHandlers) DeleteFile(c *gin.Context) {
	fileID := c.Param("id")

	file, exists := storage.GetFile(fileID)
	if !exists {
		c.JSON(http.StatusNotFound, ErrorResponse{Error: "File not found"})
		return
	}

	storage.RemoveFile(fileID)

	if file.Type == "file" && file.Path != "" {
		_ = os.Remove(file.Path)
	}

	c.JSON(http.StatusOK, SuccessResponse{Status: "success"})
}

// RenameFile handles PUT /api/files/:id/rename
func (h *FileHandlers) RenameFile(c *gin.Context) {
	fileID := c.Param("id")
	if fileID == "" {
		c.JSON(http.StatusBadRequest, ErrorResponse{Error: "Invalid file ID"})
		return
	}

	var req struct {
		NewName string `json:"newName"`
	}

	if err := c.ShouldBindJSON(&req); err != nil || req.NewName == "" {
		c.JSON(http.StatusBadRequest, ErrorResponse{
			Error: "Invalid request body or missing newName",
		})
		return
	}

	safeNewName := utils.SanitizeFilename(req.NewName)
	if safeNewName == "" {
		c.JSON(http.StatusBadRequest, ErrorResponse{Error: "Invalid new filename"})
		return
	}

	file, exists := storage.GetFile(fileID)
	if !exists {
		c.JSON(http.StatusNotFound, ErrorResponse{Error: "File not found"})
		return
	}

	oldName := file.Name
	file.Name = safeNewName
	file.UpdatedAt = time.Now()

	// 如果是文件，尝试重命名物理文件
	if file.Type == "file" && file.Path != "" {
		dir := filepath.Dir(file.Path)
		newPath := filepath.Join(dir, safeNewName)

		// 检查新文件名是否已存在
		if _, err := os.Stat(newPath); err == nil {
			// 回滚更改
			file.Name = oldName
			c.JSON(http.StatusConflict, ErrorResponse{
				Error: "A file with the new name already exists",
			})
			return
		}

		if err := os.Rename(file.Path, newPath); err != nil {
			// 回滚更改
			file.Name = oldName
			log.Printf("Failed to rename file on disk from %s to %s: %v", file.Path, newPath, err)
			c.JSON(http.StatusInternalServerError, ErrorResponse{
				Error:  "Failed to rename file on disk",
				Detail: err.Error(),
			})
			return
		}
		// 更新内存中的路径
		file.Path = newPath
	}

	c.JSON(http.StatusOK, SuccessResponse{
		Message: fmt.Sprintf("文件已成功重命名为：%s → %s", oldName, req.NewName),
		File:    file,
	})
}

// validateFileForPreview 验证文件是否可预览
func (h *FileHandlers) validateFileForPreview(file *models.File) error {
	if file.Type != "file" || file.Path == "" {
		return fmt.Errorf("not a regular file or path is empty")
	}

	valid, err := h.validateFilePath(file.Path)
	if err != nil {
		return fmt.Errorf("error validating path: %v", err)
	}
	if !valid {
		return fmt.Errorf("invalid file path")
	}

	fileInfo, err := os.Stat(file.Path)
	if err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("file not found on disk")
		}
		return fmt.Errorf("cannot access file: %v", err)
	}

	if !fileInfo.Mode().IsRegular() {
		return fmt.Errorf("target is not a regular file")
	}

	if fileInfo.Size() > maxPreviewSize {
		return fmt.Errorf("file too large to preview (max 100MB)")
	}

	ext := strings.ToLower(filepath.Ext(file.Name))
	_, isSupported := utils.SupportedPreviewExts[ext]
	if !isSupported {
		return fmt.Errorf("preview not supported for this file type")
	}

	return nil
}

// PreviewFile handles GET /api/files/:id/preview
func (h *FileHandlers) PreviewFile(c *gin.Context) {
	fileID := c.Param("id")
	if fileID == "" {
		c.JSON(http.StatusBadRequest, ErrorResponse{Error: "Invalid file ID"})
		return
	}

	file, exists := storage.GetFile(fileID)
	if !exists {
		c.JSON(http.StatusNotFound, ErrorResponse{Error: "File not found"})
		return
	}

	// 验证文件是否可预览
	if err := h.validateFileForPreview(file); err != nil {
		c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
		return
	}

	f, err := os.Open(file.Path)
	if err != nil {
		log.Printf("Preview: Failed to open file %s: %v", file.Path, err)
		c.JSON(http.StatusNotFound, ErrorResponse{Error: "File inaccessible"})
		return
	}
	defer func() {
		if closeErr := f.Close(); closeErr != nil {
			log.Printf("Preview: Error closing file %s: %v", file.Path, closeErr)
		}
	}()

	// 检测内容类型
	bufPtr := bufferPool.Get().(*[]byte)
	defer bufferPool.Put(bufPtr)
	buffer := *bufPtr

	n, _ := io.ReadFull(f, buffer[:512])
	_, _ = f.Seek(0, io.SeekStart) // Reset for ServeContent

	contentType := http.DetectContentType(buffer[:n])
	// 根据扩展名覆盖内容类型
	if contentType == "text/plain; charset=utf-8" && strings.HasSuffix(strings.ToLower(file.Name), ".js") {
		contentType = "application/javascript"
	}
	// 处理MS Office文档
	ext := strings.ToLower(filepath.Ext(file.Name))
	if strings.HasPrefix(contentType, "application/") && !strings.Contains(contentType, "json") && !strings.Contains(contentType, "javascript") {
		if ext == ".doc" || ext == ".docx" || ext == ".xls" || ext == ".xlsx" || ext == ".ppt" || ext == ".pptx" {
			mimeFromExt := mime.TypeByExtension(ext)
			if mimeFromExt != "" {
				contentType = mimeFromExt
			} else {
				contentType = "application/octet-stream"
			}
		}
	}

	c.Header("Content-Type", contentType)
	c.Header("Content-Disposition", fmt.Sprintf("inline; filename*=UTF-8''%s", url.PathEscape(file.Name)))
	c.Header("Content-Length", strconv.FormatInt(file.Size, 10))
	c.Header("Cache-Control", "public, max-age=3600")

	// 使用http.ServeContent处理Range请求、流式传输等
	http.ServeContent(c.Writer, c.Request, file.Name, file.UpdatedAt, f)
}

// validateFileForDownload 验证文件是否可下载
func (h *FileHandlers) validateFileForDownload(file *models.File) error {
	if file.Type == "folder" {
		return fmt.Errorf("cannot download folder")
	}

	if file.Path == "" {
		return fmt.Errorf("file path is empty")
	}

	valid, err := h.validateFilePath(file.Path)
	if err != nil || !valid {
		return fmt.Errorf("invalid file path")
	}

	fileInfo, err := os.Stat(file.Path)
	if err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("file not found")
		}
		return fmt.Errorf("cannot access file info: %v", err)
	}

	if !fileInfo.Mode().IsRegular() {
		return fmt.Errorf("not a regular file")
	}

	return nil
}

// DownloadFile handles GET /api/files/:id/download
func (h *FileHandlers) DownloadFile(c *gin.Context) {
	fileID := c.Param("id")
	if fileID == "" {
		c.JSON(http.StatusBadRequest, ErrorResponse{Error: "Invalid file ID"})
		return
	}

	file, exists := storage.GetFile(fileID)
	if !exists {
		c.JSON(http.StatusNotFound, ErrorResponse{Error: "File not found"})
		return
	}

	// 验证文件是否可下载
	if err := h.validateFileForDownload(file); err != nil {
		c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
		return
	}

	f, err := os.Open(file.Path)
	if err != nil {
		c.JSON(http.StatusNotFound, ErrorResponse{Error: "File not accessible"})
		return
	}
	defer f.Close()

	// 检测内容类型
	bufPtr := bufferPool.Get().(*[]byte)
	defer bufferPool.Put(bufPtr)
	buffer := *bufPtr

	n, _ := io.ReadFull(f, buffer)
	_, _ = f.Seek(0, 0)

	var contentType string
	if n == len(buffer) {
		contentType = http.DetectContentType(buffer)
	} else {
		contentType = http.DetectContentType(buffer[:n])
	}
	if contentType == "text/plain; charset=utf-8" && strings.HasSuffix(file.Name, ".js") {
		contentType = "application/javascript"
	}

	c.Header("Content-Type", contentType)
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename*=UTF-8''%s", url.PathEscape(file.Name)))
	c.Header("Content-Length", strconv.FormatInt(file.Size, 10))
	c.Header("Accept-Ranges", "bytes")
	c.Header("Cache-Control", "public, max-age=3600")

	http.ServeContent(c.Writer, c.Request, file.Name, file.UpdatedAt, f)
}
